package com.hyperf.core.lock;

import cn.hutool.core.util.IdUtil;
import com.hyperf.core.lock.exception.LockException;
import com.hyperf.core.lock.executor.LockExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;

import java.util.concurrent.TimeUnit;

/**
 * 分布式锁模板类
 *
 * @author lizhaoyang
 */
@SuppressWarnings("all")
public class LockTemplate {

    private static final Logger logger = LoggerFactory.getLogger(LockTemplate.class);

    /**
     * 默认过期时间 单位：毫秒
     */
    private static final long DEFAULT_EXPIRE = 30000L;
    /**
     * 默认获取锁超时时间 单位：毫秒
     */
    private static final long DEFAULT_ACQUIRE_TIMEOUT = 30000L;
    /**
     * 默认获取锁失败时重试时间间隔 单位：毫秒
     */
    private static final long DEFAULT_RETRY_INTERVAL = 3000L;

    /**
     * 加锁执行器
     */
    private LockExecutor lockExecutor;

    /**
     * 加锁失败策略
     */
    private LockFailureStrategy lockFailureStrategy;

    public LockTemplate() {
    }

    public LockTemplate(@NonNull LockExecutor lockExecutor, @NonNull LockFailureStrategy lockFailureStrategy) {
        this.lockExecutor = lockExecutor;
        this.lockFailureStrategy = lockFailureStrategy;
    }

    /**
     * 加锁方法
     *
     * @param key 锁key 同一个key只能被一个客户端持有
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key) {
        return lock(key, DEFAULT_EXPIRE, DEFAULT_ACQUIRE_TIMEOUT, DEFAULT_RETRY_INTERVAL);
    }

    /**
     * 加锁方法
     *
     * @param key    锁key 同一个key只能被一个客户端持有
     * @param expire 过期时间(ms) 防止死锁
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key, long expire) {
        return lock(key, expire, DEFAULT_ACQUIRE_TIMEOUT, DEFAULT_RETRY_INTERVAL);
    }

    /**
     * 加锁方法
     *
     * @param key            锁key 同一个key只能被一个客户端持有
     * @param expire         过期时间(ms) 防止死锁
     * @param acquireTimeout 尝试获取锁超时时间(ms)
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key, long expire, long acquireTimeout) {
        return lock(key, expire, acquireTimeout, DEFAULT_RETRY_INTERVAL);
    }

    /**
     * 加锁方法
     *
     * @param key            锁key 同一个key只能被一个客户端持有
     * @param expire         过期时间(ms) 防止死锁
     * @param acquireTimeout 尝试获取锁超时时间(ms)
     * @param retryInterval  获取锁失败时重试时间间隔(ms) 0:永不超时
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key, long expire, long acquireTimeout, long retryInterval) {
        // 防止重试时间大于超时时间
        if (acquireTimeout != 0 && retryInterval > acquireTimeout) {
            throw new LockException("retryInterval more than acquireTimeout, please check your configuration");
        }
        if (lockExecutor == null) {
            throw new LockException("lockExecutor can not be null, please check your configuration");
        }

        long startAcquireTime = System.currentTimeMillis();
        int acquireCount = 0;
        String lockValue = IdUtil.fastSimpleUUID();

        try {
            if (acquireTimeout <= 0) {
                while (true) {
                    acquireCount++;
                    LockInfo lockInfo = tryLock(key, expire, acquireTimeout, acquireCount, lockValue);
                    if (lockInfo != null) {
                        return lockInfo;
                    }
                    TimeUnit.MILLISECONDS.sleep(retryInterval);
                }
            } else {
                while (System.currentTimeMillis() - startAcquireTime < acquireTimeout) {
                    acquireCount++;
                    LockInfo lockInfo = tryLock(key, expire, acquireTimeout, acquireCount, lockValue);
                    if (lockInfo != null) {
                        return lockInfo;
                    }
                    TimeUnit.MILLISECONDS.sleep(retryInterval);
                }
            }
        } catch (Exception e) {
            logger.error("LockTemplate lock error, ", e);
        }

        // 加锁失败策略
        lockFailureStrategy.onLockFailure(key, acquireTimeout, acquireCount);

        return null;
    }

    private LockInfo tryLock(String key, long expire, long acquireTimeout, int acquireCount, String lockValue) {
        Object lockInstance = lockExecutor.acquire(key, lockValue, expire, acquireTimeout);
        if (lockInstance != null) {
            return new LockInfo(key, lockValue, expire, acquireTimeout, acquireCount, lockInstance);
        }
        return null;
    }

    /**
     * 释放锁
     *
     * @param lockInfo 锁信息
     * @return
     */
    public boolean releaseLock(LockInfo lockInfo) {
        return lockExecutor.releaseLock(lockInfo.getLockKey(), lockInfo.getLockValue(), lockInfo.getLockInstance());
    }

    public LockExecutor getLockExecutor() {
        return lockExecutor;
    }

    public void setLockExecutor(LockExecutor lockExecutor) {
        this.lockExecutor = lockExecutor;
    }

    public LockFailureStrategy getLockFailureStrategy() {
        return lockFailureStrategy;
    }

    public void setLockFailureStrategy(LockFailureStrategy lockFailureStrategy) {
        this.lockFailureStrategy = lockFailureStrategy;
    }
}
